package morenoapp.model.tool;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeMap;
import morenoapp.model.meta.CollectionContentType;
import morenoapp.model.meta.DoNotGenerateWrapper;
import morenoapp.model.meta.MethodDelegate;
import morenoapp.model.meta.MethodGetter;
import morenoapp.model.meta.MethodRedirect;
import morenoapp.model.meta.MethodSetter;
import morenoapp.model.meta.NoModelIsOK;
import morenoapp.model.meta.ValueIfNoModel;
import morenoapp.tools.MorenoTool;

/**
 *
 * @author dmitry.mamonov
 */
public class GenerateWrapper {
    public static void generateWrapper( Class<?> _data, Class<?> _face, Class<?> _impl, Class<?> _wrapper) throws IOException {
        boolean debug = true;
        List<String> propertiesList = new ArrayList<String>();
        StringWriter outBuffer = new StringWriter();
        PrintWriter out = new PrintWriter(outBuffer);

        TreeMap<String, Method> sortedMethods = new TreeMap();
        for (Method m : _face.getMethods()) {
            String key = "";
            for (Annotation ann : m.getAnnotations()) {
                key += ann.getClass().getSimpleName() + ",";
            }
            key += m.getName();
            if (m.getName().equals("addPropertyChangeListener") == false &&
                    m.getName().equals("removePropertyChangeListener") == false &&
                    m.getAnnotation(DoNotGenerateWrapper.class)==null) {
                sortedMethods.put(key, m);
            }
        }

        //print proxy methods
        for (Method m : sortedMethods.values()) {
            Class<?> returnType = m.getReturnType();
            Class<?> paramType = m.getParameterTypes().length > 0 ? m.getParameterTypes()[m.getParameterTypes().length-1] : Void.TYPE;

            String accessorType = Modifier.isPublic(m.getModifiers()) == true ? "public" : "unknown";
            String paramName = "";
            String paramDeclare = "";
            if (0<m.getParameterTypes().length && m.getParameterTypes().length<=2){
                if (m.getParameterTypes().length>1){
                    paramDeclare+=m.getParameterTypes()[0].getSimpleName()+" _key, ";
                }
                paramName = paramType != Void.TYPE ? "_value" : "";
                paramDeclare += (paramType != Void.TYPE ? paramType.getSimpleName() + " " : "") + paramName;

            } else if (m.getParameterTypes().length>2) {
                throw new RuntimeException("Unsupported count o arguments: "+m.getName());
            }
            String throwsList = "";
            for (Class<?> exType : m.getExceptionTypes()) {
                if (throwsList.isEmpty()) {
                    throwsList += " throws ";
                } else {
                    throwsList += ", ";
                }
                throwsList += exType.getSimpleName();
            }

            String returnTypeName = returnType.getSimpleName();
            if (m.getAnnotation(CollectionContentType.class)!=null){
                returnTypeName+="<"+m.getAnnotation(CollectionContentType.class).value().getSimpleName()+">";
            }

            out.printf("    @Override\n");
            out.printf("    %s %s %s(%s)%s {\n", accessorType, returnTypeName, m.getName(), paramDeclare, throwsList);

            if (m.getAnnotation(MethodRedirect.class) != null) {
                genRedirectCall(m, out);
            } else if (m.getAnnotation(MethodDelegate.class) != null) {
                getDelegateCall( out, returnType, m, paramName);
            } else if (m.getAnnotation(MethodSetter.class) != null) {
                genSetter(m, debug, out, paramType, paramName);
            } else if (m.getAnnotation(MethodGetter.class) != null) {
                genGetter(m, propertiesList, out, returnType, paramName);
            } else {
                System.out.println("NOT ANNOTATED: " + m.getName());
            }

            out.printf("    }\n");
            out.println();
        }

        //out.printf("    @Override\n");
        out.printf("    protected void firePropertiesAllChanged() {\n");
        for(Method m:sortedMethods.values()){
            if (m.getAnnotation(MethodGetter.class)!=null){
                if (m.getParameterTypes().length==0){
                    out.printf("        firePropertyChange(\"%s\",null, %s());\n", propByGet(m.getName()),m.getName());
                } else {
                    out.printf("        firePropertyChange(\"%s\",null, new Object());\n", propByGet(m.getName()));
                }
            }
        }
        out.printf("    }\n");

        //publish data
        List<String> lines = new ArrayList<String>();
        out.flush();
        for (String line : outBuffer.toString().split("\n")) {
            lines.add(line);
        }
        MorenoTool.updateSourceFile(_wrapper, lines);

        //update properties set
        MorenoTool.updateSourceFile(_data, propertiesList);

    }

    public static String propBySet(String _setter) {
        return _setter.substring(3, 4).toLowerCase() + _setter.substring(4);
    }

    public static String propByGet(String _getter) {
        if (_getter.startsWith("is")){
            return _getter.substring(2, 3).toLowerCase() + _getter.substring(3);
        } else {
            return _getter.substring(3, 4).toLowerCase() + _getter.substring(4);
        }
    }


    public static String getBySet(Class<?> _type, String _setter) {
        if (_type == Boolean.TYPE) {
            return "is" + _setter.substring(3);
        } else {
            return "get" + _setter.substring(3);
        }
    }
    private static void genRedirectCall(Method m, PrintWriter out) {
        if (m.getAnnotation(NoModelIsOK.class) == null) {
            out.printf("        checkModelExists();\n");
        }
        out.printf("        %s;\n", m.getAnnotation(MethodRedirect.class).value());
    }
    private static void genSetter(Method m, boolean debug, PrintWriter out, Class<?> paramType, String paramName) throws RuntimeException {
        if (m.getName().startsWith("set") == false) {
            throw new RuntimeException("Not a setter method: " + m.getName());
        }
        if (debug == true) {
            out.printf("        System.out.println(\"model.%s is set to :\"+_value);\n", propBySet(m.getName()));
        }
        //out.printf("        if (checkModelExists()==false) return;\n");
        out.printf("        checkModelExists();\n");
        String getterKey = m.getParameterTypes().length>1?"_key":"";
        out.printf("        %s oldValue = model.%s(%s);\n", paramType.getSimpleName(), getBySet(paramType, m.getName()),getterKey);
        if (getterKey.isEmpty()==false){
            getterKey+=", ";
        }
        out.printf("        model.%s(%s%s);\n", m.getName(), getterKey, paramName);
        out.printf("        firePropertyChange(\"%s\", oldValue, _value);\n", propBySet(m.getName()));
    }
    private static void getDelegateCall(PrintWriter out, Class<?> returnType, Method m, String paramName) {
        out.printf("        checkModelExists();\n");
        out.printf("        %smodel.%s(%s);\n", returnType != Void.TYPE ? "return " : "", m.getName(), paramName);
    }
    private static void genGetter(Method m, List<String> propertiesList, PrintWriter out, Class<?> returnType, String paramName) throws RuntimeException {
        if (m.getName().startsWith("get") == false && m.getName().startsWith("is") == false) {
            throw new RuntimeException("Not a getter method: " + m.getName());
        }
        propertiesList.add(String.format("    public static final String PROPERTY_%s = \"%s\";", propByGet(m.getName()).toUpperCase(), propByGet(m.getName())));
        if (m.getAnnotation(ValueIfNoModel.class) != null) {
            out.printf("        if (model==null) return %s;\n", m.getAnnotation(ValueIfNoModel.class).value());
        } else {
            out.printf("        checkModelExists();\n");
        }
        if (returnType != Void.TYPE) {
            out.printf("        return model.%s(%s);\n", m.getName(), paramName);
        } else {
            throw new RuntimeException("Getter has not return type:" + m.getName());
        }
    }
}
